﻿using System;

namespace SkeinLibManaged
{
    // The common basic and keyed core function
    public partial class Skein
    {
        /// <summary>
        /// This is the common function that drives ALL the Basic and MAC type Skein functions in all 3 state sizes.
        /// This is NOT USED by the end-coder.
        /// To use in a non-MAC oriented fashion, pass null/"Nothing" to MACKey or MACKeyByteLen = 0.
        /// It is assumed that the stub functions that call this common function have already done input error checking.
        /// </summary>
        /// <param name="stateBitWidth">Narrow (256), Medium (512), or Wide (1024)</param>
        /// <param name="input">The input byte array.</param>
        /// <param name="MACKey">The key byte array for producing MAC's.</param>
        /// <param name="hashBitLength">The resulting Hash Bit Length.  Between 8 and 65536 bits, in 8 bit increments.</param>
        /// <returns>Returns byte array containing hash result.</returns>
        internal static byte[] SkeinBKCommonBytes(StateSize stateBitWidth, byte[] input, byte[] MACKey,
                                        UInt16 hashBitLength)
        {
            UInt64[] tweak = { 0, 0 };

            // break out the ENUM into it's root numerical type and store the result as 256, 512, or 1024
            // this could also be done with a "switch"
            UInt16 BYTEWIDTH = (UInt16)(UInt16.Parse(System.Enum.Format(typeof(StateSize), stateBitWidth, "D")) / 8);
            
            UInt16 WORDWIDTH = (UInt16)(BYTEWIDTH / sizeof(UInt64));
            UInt64[] state = new UInt64[WORDWIDTH];
            UInt64[] transport = new UInt64[WORDWIDTH];
            ClearWords(state);

            //BEGIN KEYING FOR MAC
            if (MACKey != null && MACKey.Length > 0)
            {
                tweak[1] = SKEIN_FLAG_FIRST + SKEIN_TYPE_KEY;
                for (long i = 0; (i * BYTEWIDTH) < MACKey.Length; i++)
                {
                    //process a block
                    tweak[0] = (ulong)(i * BYTEWIDTH);
                    byte[] buf = new byte[BYTEWIDTH];
                    for (uint j = 0; j < BYTEWIDTH; j++)
                    {
                        //process a byte
                        if ((j + (i * BYTEWIDTH)) < MACKey.Length)
                        {
                            buf[j] = MACKey[(i * BYTEWIDTH) + j];
                            tweak[0] += 1;  //count each byte
                        }
                        else	// or zero it out
                            buf[j] = 0;
                    }
                    if ((i + 1) * BYTEWIDTH >= MACKey.Length)
                        tweak[1] += SKEIN_FLAG_FINAL;
                    //stuff the transport in prep for transformation
                    System.Buffer.BlockCopy(buf, 0, transport, 0, buf.Length);
                    switch (stateBitWidth)
                    {
                        case StateSize.Narrow: state = ThreeFish.ThreeFish256Words(transport, state, tweak, true); break;
                        case StateSize.Medium: state = ThreeFish.ThreeFish512Words(transport, state, tweak, true); break;
                        case StateSize.Wide: state = ThreeFish.ThreeFish1024Words(transport, state, tweak, true); break;
                    }
                    tweak[1] &= ~SKEIN_FLAG_FIRST;
                }
            }
            //END KEYING

            //BEGIN CONFIG
            ClearWords(transport);
            transport[0] = SKEIN_VS;
            transport[1] = hashBitLength;

            tweak[0] = 32;
            tweak[1] = SKEIN_FLAG_FIRST + SKEIN_FLAG_FINAL + SKEIN_TYPE_CONFIG;

            switch (stateBitWidth)
            {
                case StateSize.Narrow: state = ThreeFish.ThreeFish256Words(transport, state, tweak, true); break;
                case StateSize.Medium: state = ThreeFish.ThreeFish512Words(transport, state, tweak, true); break;
                case StateSize.Wide: state = ThreeFish.ThreeFish1024Words(transport, state, tweak, true); break;
            }
            //END CONFIG

            //BEGIN MSG
            tweak[1] = SKEIN_FLAG_FIRST + SKEIN_TYPE_MSG;
            if (input != null && input.Length > 0)
            {
                for (long i = 0; (i * BYTEWIDTH) < input.Length; i++)
                {
                    //process a block
                    tweak[0] = (UInt64)(i * BYTEWIDTH);
                    byte[] buf = new byte[BYTEWIDTH];
                    for (uint j = 0; j < BYTEWIDTH; j++)
                    {
                        //process a byte
                        if ((j + (i * BYTEWIDTH)) < input.Length)
                        {
                            buf[j] = input[(i * BYTEWIDTH) + j];
                            tweak[0] += 1;  //count each byte
                        }
                        else	// or zero it out
                            buf[j] = 0;
                    }
                    if ((i + 1) * BYTEWIDTH >= input.Length)
                        tweak[1] += SKEIN_FLAG_FINAL;
                    //stuff transport in prep for transformation
                    System.Buffer.BlockCopy(buf, 0, transport, 0, buf.Length);
                    switch (stateBitWidth)
                    {
                        case StateSize.Narrow: state = ThreeFish.ThreeFish256Words(transport, state, tweak, true); break;
                        case StateSize.Medium: state = ThreeFish.ThreeFish512Words(transport, state, tweak, true); break;
                        case StateSize.Wide: state = ThreeFish.ThreeFish1024Words(transport, state, tweak, true); break;
                    }
                    tweak[1] &= ~SKEIN_FLAG_FIRST;
                }
            }
            else
            {
                tweak[0] = 0;
                tweak[1] = SKEIN_FLAG_FIRST + SKEIN_TYPE_MSG + SKEIN_FLAG_FINAL;
                ClearWords(transport);
                switch (stateBitWidth)
                {
                    case StateSize.Narrow: state = ThreeFish.ThreeFish256Words(transport, state, tweak, true); break;
                    case StateSize.Medium: state = ThreeFish.ThreeFish512Words(transport, state, tweak, true); break;
                    case StateSize.Wide: state = ThreeFish.ThreeFish1024Words(transport, state, tweak, true); break;
                }
            }
            //END MSG

            //BEGIN OUTPUT
            UInt16 byteCount = (UInt16)((hashBitLength + 7) >> 3);
            tweak[0] = 8; // <<<<<<<<<<<<<<WHY???????? not explained well in the white paper or ref code
            tweak[1] = SKEIN_FLAG_FIRST + SKEIN_TYPE_OUTPUT + SKEIN_FLAG_FINAL;
            byte[] result = new byte[hashBitLength / 8];
            for (int i = 0; (i * BYTEWIDTH) < result.Length; i++)
            {   //use the input array for a counter, only the state and tweak are used and remain constant
                ClearWords(transport);
                transport[0] = (UInt64)i;
                switch (stateBitWidth)
                {
                    case StateSize.Narrow: transport = ThreeFish.ThreeFish256Words(transport, state, tweak, true); break;
                    case StateSize.Medium: transport = ThreeFish.ThreeFish512Words(transport, state, tweak, true); break;
                    case StateSize.Wide: transport = ThreeFish.ThreeFish1024Words(transport, state, tweak, true); break;
                }
                //dump the transport to the output, then discard
                int nextPosition = (i + 1) * BYTEWIDTH;
                int currentPosition = i * BYTEWIDTH;
                if (nextPosition <= result.Length)
                    System.Buffer.BlockCopy(transport, 0, result, currentPosition, (int)BYTEWIDTH);
                else
                    System.Buffer.BlockCopy(transport, 0, result, currentPosition, (int)(result.Length - currentPosition));
            }
            //zero out used memory
            ClearWords(transport);
            ClearWords(state);
            ClearWords(tweak);
            //END OUTPUT
            return result;
        }//end Skein common
    }// end Class
}// end Namespace